// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Text;
using System.IO;
using System.Globalization;
using System.Xml;

using Microsoft.TeamFoundation.Migration.Toolkit.Wit;
using Microsoft.TeamFoundation.Migration.Toolkit;

namespace Microsoft.TeamFoundation.Migration.WorkItemTracking.Wss
{
    using MigrationFileAttachmentOperation = SyncActionContainer<IMigrationFileAttachment>;

    /// <summary>
    /// An update to a Wss work item (task).  
    /// An update either represents an update to a task or file attachments.  
    /// It cannot do both (per the implementation of the WssMigrationWorkItemStore.Update method).
    /// </summary>
    internal sealed class WssWorkItemUpdate : IWorkItemUpdate
    {
        private Watermark m_source;                         // Source revision
        private MigrationRevision m_rev;                    // Revision data.

        private WssHelper m_helper;                         // Wss helper
        private Watermark m_target;                         // Target work item; used for attachments
        private Collection<MigrationFileAttachmentOperation> m_attachOps;// List of attachment operations

        /// <summary>
        /// Target watermark.
        /// </summary>
        public Watermark Target { get { return m_target; } }


        #region IWorkItemUpdate Members

        /// <summary>
        /// Returns revision of the original work item the update is supposed to bring.
        /// </summary>
        public int SourceRevision { get { return 1; } }

        /// <summary>
        /// Specifies target revision for the update.
        /// Called by the toolkit/engine to make sure the ID is set for the when an 
        /// update is made to a work item that is added as part of the migration.
        /// </summary>
        /// <param name="watermark">Target revision</param>
        public void SetTarget(Watermark watermark)
        {
            if (watermark == null)
            {
                throw new ArgumentNullException("watermark");
            }
            m_target = watermark;
        }

        #endregion


        #region Constructors

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="xdoc">Xml document for the update</param>
        /// <param name="rev">Revision info</param>
        /// <param name="target">Target that should be updated (null for new items)</param>
        public WssWorkItemUpdate(WssHelper helper, MigrationRevision rev, Watermark target)
        {
            m_rev = rev;
            m_source = rev.Source;
            m_target = target;
            m_helper = helper;
        }

        /// <summary>
        /// Constructor for attachments.
        /// </summary>
        /// <param name="helper">Wss helper</param>
        /// <param name="target">Work item the attachments go to</param>
        /// <param name="attachOps">List of attachment operations</param>
        public WssWorkItemUpdate(WssHelper helper, Collection<MigrationFileAttachmentOperation> attachOps)
        {
            m_helper = helper;
            m_attachOps = attachOps;
        }
        #endregion


        #region Public Methods

        /// <summary>
        /// Checks whether update modifies fields (not attachments)
        /// </summary>
        public bool IsFieldUpdate { get { return m_rev != null; } }

        /// <summary>
        /// Stores udate in the XML batch.
        /// </summary>
        /// <param name="batch">XML batch</param>
        /// <returns>Cookie</returns>
        public string JoinBatch(XmlElement batch)
        {
            Dictionary<string, bool> fieldsMap = new Dictionary<string,bool>(StringComparer.InvariantCulture);
            string commandId = Guid.NewGuid().ToString();
            string commandName = GetCommand();

            // Operations on attachments should be in a separate revision
            Debug.Assert(m_attachOps == null, "Attachment operations in a regular revision!");

            XmlElement e = batch.OwnerDocument.CreateElement("Method");
            e.SetAttribute("ID", commandId);
            e.SetAttribute("Cmd", commandName);

            for (int i = 0; i < m_rev.Fields.Count; i++)
            {
                MigrationField f = m_rev.Fields[i];
                if (!WssFields.IsReadOnly(f.Name))
                {
                    m_helper.SubmitField(e, f.Name, f.Value);
                    fieldsMap.Add(f.Name, true);
                }
            }
            if (m_target == null)
            {
                // Add all fields that were not specified to prevent them from getting default values
                for (int i = 0; i < WssFields.AllFields.Length; i++)
                {
                    string field = WssFields.AllFields[i];
                    if (!fieldsMap.ContainsKey(field) && !WssFields.IsReadOnly(field))
                    {
                        m_helper.SubmitField(e, field, null);
                    }
                }
            }
            else
            {
                m_helper.SubmitField(e, WssFields.Id, m_target.Id);
            }
            batch.AppendChild(e);
            return string.Format(CultureInfo.InvariantCulture, "{0},{1}", commandId, commandName);
        }


        /// <summary>
        /// Do all the add and delete actions for the attachments.
        /// </summary>
        public UpdateResult DoAttachmentOperations()
        {
            try
            {
                foreach (MigrationFileAttachmentOperation item in m_attachOps)
                {
                    if (item.Action == AssociationAction.Add)
                    {
                        byte[] contents;
                        using (Stream strm = item.Item.GetFileContents())
                        {
                            contents = GetBytesFromStream(strm);
                        }
                        m_helper.AttachFile(m_target.Id, item.Item.Name, contents);
                    }
                    else
                    {
                        Debug.Assert(item.Action == AssociationAction.Remove, "Unsupported action!");
                        WssMigrationFileAttachment a = (WssMigrationFileAttachment)item.Item;
                        m_helper.RemoveFiles(m_target.Id, a.Url);
                    }
                }

                return new UpdateResult(m_target);
            }
            catch (Exception e)
            {
                return new UpdateResult(e);
            }
        }
        #endregion


        #region Private Functions

        /// <summary>
        /// Gets the WSS command associated with the update.
        /// </summary>
        /// <returns>WSS command</returns>
        private string GetCommand()
        {
            return m_target == null ? "New" : "Update";
        }

        /// <summary>
        /// Reads the bytes from a stream
        /// </summary>
        /// <param name="strm">Stream</param>
        /// <returns></returns>
        private static byte[] GetBytesFromStream(Stream strm)
        {
            if (strm.Length >= WssHelper.MaxAttachmentLength)
            {
                throw new WssStoreException(Resources.ErrorFileTooBig);
            }
            byte[] bytes = new byte[strm.Length];
            strm.Read(bytes, 0, Convert.ToInt32(strm.Length));
            strm.Close();
            return bytes;
        }

        #endregion
    }
}
